-
Notifications
You must be signed in to change notification settings - Fork 375
refactor: [M3-9647] - Reduce api requests made for every keystroke in Volume attach drawer #12052
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
refactor: [M3-9647] - Reduce api requests made for every keystroke in Volume attach drawer #12052
Conversation
β¦ Volume attach drawer
β¦647-reduce-api-calls-for-linode-volume
β¦me attach drawer
const { data: volume } = useVolumeQuery( | ||
values.volume_id, | ||
values.volume_id !== -1 | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enabled the useVolumeQuery
only when Volume_id
is set and not -1(default value)
Cloud Manager UI test resultsπΊ 2 failing tests on test run #2 βοΈ
Details
TroubleshootingUse this command to re-run the failing tests: pnpm cy:run -s "cypress/e2e/core/stackscripts/smoke-community-stackscripts.spec.ts,cypress/e2e/core/objectStorageMulticluster/bucket-details-multicluster.spec.ts" |
const searchFilter = inputValue | ||
? { | ||
'+or': [ | ||
{ label: { '+contains': inputValue } }, | ||
{ tags: { '+contains': inputValue } }, | ||
], | ||
} | ||
: {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing the API searching functionality makes it so that if the user searches for a result that isn't on the first page of paginated results, the result does not show.
Screen.Recording.2025-04-17.at.10.15.43.AM.mov
We need to decide if we want to keep the API pagination/filtering or go full client side search/filtering. My vote would be to keep this component using API pagination/filtering.
Even better, we could use @linode/search
to provide the user even more filtering functionality as an easter egg π₯
The implementation would look something like
const [inputValue, setInputValue] = React.useState<string>('');
const debouncedSearchValue = useDebouncedValue(inputValue);
const { filter: searchFilter } = getAPIFilterFromQuery(debouncedSearchValue, {
searchableFieldsWithoutOperator: ['label', 'tags'],
});
const { data, fetchNextPage, hasNextPage, isLoading } =
useInfiniteVolumesQuery({
...searchFilter,
...(region ? { region } : {}),
'+order': 'asc',
// linode_id: null, <- if the API let us, we would do this
'+order_by': 'label',
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if I'm using API side filtering instead of client side filtering, the searched/user-typed option is not shown if it is not present on the first page π€
Screen.Recording.2025-04-22.at.11.16.02.PM.mov
I'm not able to think about an approach to solve this other than fetching all pages data at once(which obviously isn't very efficient)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can get things working better by fine turning when we call setInputValue("")
?
Code
import { useInfiniteVolumesQuery } from '@linode/queries';
import { getAPIFilterFromQuery } from '@linode/search';
import { Autocomplete } from '@linode/ui';
import { useDebouncedValue } from '@linode/utilities';
import * as React from 'react';
interface Props {
disabled?: boolean;
error?: string;
name: string;
onBlur: (e: any) => void;
onChange: (volumeId: null | number) => void;
region?: string;
value: number;
}
export const VolumeSelect = (props: Props) => {
const { disabled, error, name, onBlur, onChange, region, value } = props;
const [inputValue, setInputValue] = React.useState<string>('');
const debouncedSearchValue = useDebouncedValue(inputValue);
const { filter: searchFilter } = getAPIFilterFromQuery(debouncedSearchValue, {
searchableFieldsWithoutOperator: ['label', 'tags'],
});
const { data, fetchNextPage, hasNextPage, isLoading } =
useInfiniteVolumesQuery({
...searchFilter,
...(region ? { region } : {}),
'+order': 'asc',
// linode_id: null, <- if the API let us, we would do this
'+order_by': 'label',
});
const options = data?.pages
.flatMap((page) => page.data)
.map(({ id, label }) => ({ id, label }));
const selectedVolume = options?.find((option) => option.id === value) ?? null;
return (
<Autocomplete
disabled={disabled}
errorText={error}
helperText={
region && "Only volumes in this Linode's region are attachable."
}
id={name}
inputValue={selectedVolume ? selectedVolume.label : inputValue}
isOptionEqualToValue={(option) => option.id === selectedVolume?.id}
label="Volume"
ListboxProps={{
onScroll: (event: React.SyntheticEvent) => {
const listboxNode = event.currentTarget;
if (
listboxNode.scrollTop + listboxNode.clientHeight >=
listboxNode.scrollHeight &&
hasNextPage
) {
fetchNextPage();
}
},
}}
loading={isLoading}
onBlur={onBlur}
onChange={(event, value) => {
onChange(value?.id ?? null);
}}
onInputChange={(event, value, reason) => {
if (reason === 'input') {
setInputValue(value);
}
if (reason === 'clear' || reason === 'blur') {
setInputValue('');
}
}}
options={options ?? []}
placeholder="Select a Volume"
value={selectedVolume}
/>
);
};
If that doesn't work, maybe we can find a way to make our API filter include the currently selected volume even when the inputValue is ""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also in favor of retaining API pagination/filtering in this component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that code I provided has too many edge cases / issues, I have another idea π‘
@@ -47,6 +32,15 @@ export const VolumeSelect = (props: Props) => { | |||
|
|||
return ( | |||
<Autocomplete | |||
disabled={disabled} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we retain API filtering, we should add this prop
disabled={disabled} | |
filterOptions={(options, state) => options |
to the Autocomplete to ensure it doesn't do any client side filtering
Description π
CM fires an API request for every keystroke in Volume attach drawer.
Changes π
Target release date ποΈ
N/A
Preview π·
Screen.Recording.2025-04-17.at.5.02.43.PM.mov
Screen.Recording.2025-04-17.at.5.07.32.PM.mov
How to test π§ͺ
Reproduction steps
/volumes
request fired on each key storeVerification steps
/volumes
request is only fired on the initial render of the drawerAuthor Checklists
As an Author, to speed up the review process, I considered π€
π Doing a self review
β Our contribution guidelines
π€ Splitting feature into small PRs
β Adding a changeset
π§ͺ Providing/improving test coverage
π Removing all sensitive information from the code and PR description
π© Using a feature flag to protect the release
π£ Providing comprehensive reproduction steps
π Providing or updating our documentation
π Scheduling a pair reviewing session
π± Providing mobile support
βΏ Providing accessibility support
As an Author, before moving this PR from Draft to Open, I confirmed β